package de.slothsoft.games;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;

import javax.swing.JPanel;

public class GamePanel extends JPanel {

	private static final long REPAINT_TIMEOUT = 1000;
	private static final long REPAINT_IN_MS = 1000 / 24; // 24 frames per second
	private static final long serialVersionUID = -4607180702570402004L;

	private final GameEvent gameEvent = new GameEvent();
	private GameStatus status = GameStatus.NOT_STARTED;
	private long lastExecutionTime;
	private Game game;

	public GamePanel(Game game) {
		this.game = game;

		KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
		manager.addKeyEventDispatcher(new KeyDispatcher());
	}

	public void start(Game newGame) {
		setGame(newGame);
		start();
	}

	public void start() {
		if (this.game == null) {
			throw new UnsupportedOperationException("Set game before starting!"); //$NON-NLS-1$
		}
		this.status = GameStatus.STARTED;
		this.game.init();
	}

	public void pause() {
		this.status = GameStatus.PAUSED;
	}

	public void restart() {
		this.status = GameStatus.STARTED;
	}

	public void stop() {
		this.status = GameStatus.STOPPED;
		this.game.close();
		this.game = null;
	}

	@Override
	public Dimension getPreferredSize() {
		if (this.game.getSize() != null) {
			return this.game.getSize();
		}
		return super.getPreferredSize();
	}

	@Override
	public void paint(Graphics g) {
		if (!shouldPaint()) {
			return;
		}

		long executionTime = System.currentTimeMillis();
		updateGameEvent(g, executionTime);

		if (this.gameEvent.getDelta() < REPAINT_TIMEOUT) {
			this.game.update(this.gameEvent);
			this.game.render(this.gameEvent);
		}

		this.lastExecutionTime = executionTime;
		finishUpGameUpdate();
	}

	private void updateGameEvent(Graphics g, long executionTime) {
		int delta = this.lastExecutionTime == 0 ? 0 : (int) (executionTime - this.lastExecutionTime);
		this.gameEvent.setGraphics((Graphics2D) g);
		this.gameEvent.setDelta(delta);
	}

	private void finishUpGameUpdate() {
		this.gameEvent.getInput().releaseTypedKeys();

		if (shouldRepaint()) {
			repaint(REPAINT_IN_MS);
		}
	}

	private boolean shouldRepaint() {
		return this.status == GameStatus.STARTED;
	}

	private boolean shouldPaint() {
		return this.status == GameStatus.STARTED || this.status == GameStatus.PAUSED;
	}

	GameEvent getGameEvent() {
		return this.gameEvent;
	}

	Input getInput() {
		return getGameEvent().getInput();
	}

	public void setGame(Game game) {
		this.game = game;
	}

	public Game getGame() {
		return this.game;
	}

	/*
	 * 
	 */

	protected class KeyDispatcher implements KeyEventDispatcher {

		private int lastPressedKey;

		@Override
		public boolean dispatchKeyEvent(KeyEvent e) {
			if (e.getID() == KeyEvent.KEY_PRESSED) {
				this.lastPressedKey = e.getKeyCode();
				getInput().pressedKey(this.lastPressedKey);
			} else if (e.getID() == KeyEvent.KEY_RELEASED) {
				getInput().releasedKey(e.getKeyCode());
			} else if (e.getID() == KeyEvent.KEY_TYPED) {
				getInput().typedKey(this.lastPressedKey);
			}
			return false;
		}
	}

}
